from pywebio.input import file_upload
from retry import retry
import requests
import json
from pathlib import Path
import re
import os
import sys
import time
from enum import Enum
from bs4 import BeautifulSoup

class DownloadStatus(Enum):
    Idle = 0
    Working = 1
    Finish = 2


class BilibiliDownloader:
    def __init__(self, bv_id, download_dir, callback=None):
        self.bv_id = bv_id
        self.__download_dir = download_dir
        self.__callback = callback
        self.start_time = self.__getlocaltime()
        self.end_time = self.__getlocaltime()
        self.__GetVideoInfo()

    @retry(tries=5, delay=5)
    def start(self):
        self.__DownloadVideo()
        return 1

    debug = False
    start_time = ''
    end_time = ''
    bv_id = ""
    title = ""
    page_count = 0
    up = ""
    all_file_size = ""
    current_title = ""
    __video_pages = list()
    __download_dir = ""
    __callback = None

    def __do_callback(self, status: DownloadStatus, process: float):
        if self.__callback != None:
            self.__callback(self, status, process)
            pass

    def __getlocaltime(self):
        return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))

    def __create_dir_if_not_exist(self, path):
        if path == '':
            return
        if not os.path.exists(path):
            os.makedirs(path)

    def __bytes2human(self, n):
        symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')  # 元组保存
        prefix = {}
        for i, s in enumerate(symbols):
            prefix[s] = 1 << (i + 1) * 10  # 各量级对应位数
        for s in reversed(symbols):  # 反转元组，以最快找出符合的量级
            if n >= prefix[s]:  # 遇到符合的量级则计算后return
                value = float(n) / prefix[s]
                return '%.1f%s' % (value, s)  # 按%.1f格式返回字符串
        return "%sB" % n  # 小于以上量级，直接返回B

    def __GetValidateFileName(self, file_name):
        rstr = r"[\/\\\:\*\?\"\<\>\|]"  # '/ \ : * ? " < > |'
        new_file_name = re.sub(rstr, "_", file_name)  # 替换为下划线
        return new_file_name

    def __GetVideoInfo(self):
        params = (
            ('bvid', self.bv_id),
        )
        response = requests.get(
            'http://api.bilibili.com/x/web-interface/view', params=params)

        js = json.loads(response.text)
        self.page_count = js['data']['videos']
        self.up = js['data']['owner']['name']
        self.title = self.__GetValidateFileName(js['data']['title'])

        params = (
            ('bvid', self.bv_id),
        )
        response = requests.get(
            'http://api.bilibili.com/x/player/pagelist', params=params)
        js = json.loads(response.text)
        self.__video_pages.clear()
        for inx, page in enumerate(js['data']):
            print(inx)
            if inx > 50:
                page_info = dict()
                page_info['page'] = page['page']
                page_info['title'] = self.__GetValidateFileName(page['part'])
                self.__video_pages.append(page_info)

    def __DownloadVideo(self):
        real_dir = self.__download_dir
        if self.page_count > 1:
            real_dir = self.__download_dir + \
                '/' + self.title

        dir_download = Path(real_dir)
        self.__create_dir_if_not_exist(dir_download)

        if self.debug:
            print('[%s] begin   %s' %
                  (self.__getlocaltime(), self.title), flush=True)

        all_file_size = 0
        for page in self.__video_pages:
            url = "https://www.bilibili.com/video/%s?p=%d" % (
                self.bv_id, page['page'])
            title = self.title
            self.current_title = self.title
            if self.page_count > 1:
                title = page['title']
                self.current_title = self.current_title + '\n' + page['title']
            file_path = real_dir + '/' + title + '.mp4'
            self.__do_callback(DownloadStatus.Working, 0.0)
            self.__current_url = url
            self.__current_file_path = file_path
            file_size = self.__download()
            content_size_human = self.__bytes2human(file_size)
            all_file_size += file_size
            if self.debug:
                print('[%s] [%d]%s   %s' %
                      (self.__getlocaltime(), page['page'], title, content_size_human), flush=True)

        self.all_file_size = self.__bytes2human(all_file_size)
        if self.debug:
            print('[%s] end   %s   %s' % (self.__getlocaltime(),
                                          self.title, self.all_file_size), flush=True)
        self.current_title = ''
        self.end_time = self.__getlocaltime()
        self.__do_callback(DownloadStatus.Finish, 100.0)

    def __resolve_video_path(self, bv_url):
        time.sleep(5)
        session = requests.session()
        response = session.get('https://bilibili.syyhc.com')

        # 接下来使用bs4解析这个页面
        soup = BeautifulSoup(response.text, 'html.parser')
        tag = soup.select('#csrf_token', limit=1)
        csrf_token = tag[0]['value']

        headers = {
            'Connection': 'keep-alive',
            'Pragma': 'no-cache',
            'Cache-Control': 'no-cache',
            'Upgrade-Insecure-Requests': '1',
            'Origin': 'https://bilibili.syyhc.com',
            'Content-Type': 'application/x-www-form-urlencoded',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36 Edg/86.0.622.43',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
            'Sec-Fetch-Site': 'same-origin',
            'Sec-Fetch-Mode': 'navigate',
            'Sec-Fetch-User': '?1',
            'Sec-Fetch-Dest': 'document',
            'Referer': 'https://bilibili.syyhc.com/',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
        }

        data = {
            'url': bv_url,
            'go': '',
            'csrf_token': csrf_token
        }

        response = session.post(
            'https://bilibili.syyhc.com/parser', headers=headers,  data=data)

        soup = BeautifulSoup(response.text, 'html.parser')
        tag = soup.find(name='source')   
        video_src = tag['src']
        return video_src

    @retry(tries=5, delay=5)
    def __download(self):
        url = self.__current_url
        file_path = self.__current_file_path
        url = self.__resolve_video_path(url)
        headers = {
            'authority': 'upos-sz-mirrorks3.bilivideo.com',
            'pragma': 'no-cache',
            'cache-control': 'no-cache',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36 Edg/86.0.622.43',
            'accept': '*/*',
            'sec-fetch-site': 'cross-site',
            'sec-fetch-mode': 'no-cors',
            'sec-fetch-dest': 'video',
            'referer': 'https://bilibili.syyhc.com/',
            'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
            'range': 'bytes=0-',
        }

        with requests.get(url, headers=headers, stream=True) as r:
            content_size = int(r.headers['content-length'])
            downloaded_size = 0.0
            fragment_size = 4096*3
            with open(file_path, 'wb') as f:
                for i in r.iter_content(chunk_size=fragment_size):
                    downloaded_size += fragment_size
                    self.__do_callback(DownloadStatus.Working,
                                       downloaded_size/content_size)
                    f.write(i)

        return content_size


def GetTitleByBvId(bv_id):
    params = (
        ('bvid', bv_id),
    )
    response = requests.get(
        'http://api.bilibili.com/x/web-interface/view', params=params)

    js = json.loads(response.text)
    try:
        if bv_id == js['data']['bvid']:
            return js['data']['title']
        else:
            return ''
    except:
        return ''


def callback(downloader: BilibiliDownloader, status: DownloadStatus, process: float):
    if status == DownloadStatus.Finish:
        print("title:%s up:%s count:%d size:%s" %
              (downloader.title, downloader.up, downloader.page_count, downloader.all_file_size), flush=True)


if __name__ == '__main__':
    task = BilibiliDownloader('BV1hf4y1g7hb', '/srv/dev-disk-by-uuid-12c86163-23a1-4da5-b6b9-a52b69ec948a/RSYNC备份', callback)
    task.start()
    pass
